package com.zuilapsa.jmc.model;

import java.util.*;

import org.dom4j.*;

import com.zuilapsa.jmc.*;
import com.zuilapsa.jmc.actions.*;
import com.zuilapsa.jmc.userInterfaces.*;

public class Level implements Token, HelpInterface {
	protected static Level root = null;

	Level parent = null;

	Element config;

	Element definition;

	Hashtable<String, Token> tokens = new Hashtable<String, Token>();

	Hashtable<String, ActionInterface> actions = new Hashtable<String, ActionInterface>();

	Hashtable<String, Level> levels = new Hashtable<String, Level>();

	// Hashtable<String, DefinitionWrapper> levelGlobs = new Hashtable<String,
	// DefinitionWrapper>();

	Hashtable<String, Property> properties = new Hashtable<String, Property>();

	// Hashtable<String, DefinitionWrapper> propertyGlobs = new
	// Hashtable<String, DefinitionWrapper>();

	Hashtable<String, DefinitionWrapper> definitions = new Hashtable<String, DefinitionWrapper>();

	public Level(Level parent, Element definition, String name) {
		setParent(parent);
		setDefinition(definition);
		setConfig(getDefaultConfigElement(getSequenceFromName(name)));
//		parseDefinition();
//		parseConfig();
		parseConfigAndDefinition();
	}

	public Level(Level parent, Element definition, int sequence) {
		setParent(parent);
		setDefinition(definition);
		setConfig(getDefaultConfigElement(sequence));
//		parseDefinition();
//		parseConfig();
		parseConfigAndDefinition();
	}

	public Level(Level parent, Element definition) {
		setParent(parent);
		setDefinition(definition);
		setConfig(getDefaultConfigElement(-1));
//		parseDefinition();
//		parseConfig();
		parseConfigAndDefinition();
	}

	public Level(Level parent, Element definition, Element config) {
		setParent(parent);
		setDefinition(definition);
		setConfig(config);
//		parseDefinition();
//		parseConfig();
		parseConfigAndDefinition();
	}

	protected void setConfig(Element config) {
		this.config = config;
	}
	protected void setDefinition(Element definition) {
		this.definition = definition;
	}


	protected void parseConfigAndDefinition() {
		// add all definitions to hash
		for (Iterator i = definition.elementIterator(); i.hasNext();) {
			Element sub = (Element) i.next();
			addSubDefinition(sub);
		}

		// browse through definitions to add existing config entries that are
		// defined
		// in case of glob definition add also all maching config entries

		// start from inherited actions
		String inheritJoined = getDefinitionElement().attributeValue("inherit");
		if (inheritJoined != null) {
			String[] inheritArray = inheritJoined.split("\\|");
			for (int i = 0; i < inheritArray.length; i++) {
				String actionName = inheritArray[i];
				ActionInterface action = (ActionInterface) getParent()
						.getToken(actionName);
				if (action != null)
					addAction(action);
				else {
					System.err.println(Configuration
							.listPathToString(getPath()));
					System.err.println("inherited action not found: "
							+ actionName);
				}
			}
		}

		// continue with actions based on definitions
		Collection<DefinitionWrapper> defs = getSubDefinitions().values();
		for (Iterator<DefinitionWrapper> i = defs.iterator(); i.hasNext();) {
			DefinitionWrapper def = i.next();
			try {
				if (def.isAction()) {
					ActionInterface a = ActionProducer
							.getAction(def.definition);
					addAction(a);
				}
			} catch (JmcException e) {
				System.err.println("Error initializing action: "
						+ def.getName());
			}
		}

		// continue with properties
		for (Iterator<DefinitionWrapper> i = defs.iterator(); i.hasNext();) {
			DefinitionWrapper def = i.next();
			if (def.isProperty()) {
				if (def.isGlob()) {
					//TODO some other time in some other live: add default instancies for globs
					List<Element> globProperties = getSubElementsMatchingGlob(config, def.getName());
					for (Element propertyConfig : globProperties) {
						addProperty(new Property(def.definition,propertyConfig));
					}
				} else {
					Element propertyConfig = getSubElementWithName(config, def.getName());
					if (propertyConfig == null)
						addProperty(new Property(def.definition));
					else 
						addProperty(new Property(def.definition,propertyConfig));
				}
			}
		}
		
		// continue with levels
		for (Iterator<DefinitionWrapper> i = defs.iterator(); i.hasNext();) {
			DefinitionWrapper def = i.next();
			if (def.isLevel()) {
				if (def.isGlob()) {
					//TODO some other time in some other live: add default instancies for globs
					List<Element> globLevels = getSubElementsMatchingGlob(config, def.getName());
					for (Element levelConfig : globLevels) {
						addLevel(new Level(this,def.definition,levelConfig));
					}
				} else {
					Element levelConfig = getSubElementWithName(config, def.getName());
					if (levelConfig == null)
						addLevel(new Level(this,def.definition));
					else 
						addLevel(new Level(this,def.definition,levelConfig));
				}
			}
		}
		
		// remove elements from config that aren't defined, or are wrongly defined
		for (Iterator i = config.elementIterator("property"); i.hasNext();) {
			Element el = (Element)i.next();
			String name = el.attributeValue("name");
			DefinitionWrapper def = getSubDefinition(name);
			if (def == null || !def.isProperty())
				config.remove(el);
		}

		for (Iterator i = config.elementIterator("level"); i.hasNext();) {
			Element el = (Element)i.next();
			String name = el.attributeValue("name");
			DefinitionWrapper def = getSubDefinition(name);
			if (def == null || !def.isLevel())
				config.remove(el);
		}
		
		// fin - whole tree is initialized
	}

/*	protected void parseConfig() {
		try {
			// adds all properties in config file
			// does not validate if definitions exist
			for (Iterator i = config.elementIterator("property"); i.hasNext();) {
				Element el = (Element) i.next();
				if (hasSubDefinition(el.attributeValue("name"))) {
					Property p = new Property(getSubDefinition(el
							.attributeValue("name")).definition, el);
					addProperty(p);
				} else {
					config.remove(el);
				}
			}

			// adds all sublevels based on config (not validating)
			for (Iterator i = config.elementIterator("level"); i.hasNext();) {
				Element el = (Element) i.next();
				Level token = new Level(this, getSubDefinition(el
						.attributeValue("name")).definition, el);
				addLevel(token);
			}
		} catch (Exception e) {
			System.err.println("exception: " + e.getLocalizedMessage());
		}
		if (parent == null) {
			try {
				if (Configuration.getInstance().getRootConfigElement() != config) {
					Configuration.getInstance().configDoc
							.setRootElement(config);
					System.err.println("I've just replaced root config with: "
							+ config.asXML());
				}
			} catch (JmcException e) {
				System.err
						.println("This shall never occur e:" + e.getMessage());
			}
		}
	}


	protected void parseDefinition() {
		for (Iterator i = definition.elementIterator(); i.hasNext();) {
			Element sub = (Element) i.next();
			addSubDefinition(sub);
		}
		// initialize default empty config element
		// setConfig(getDefaultConfigElement(-1));

		// import inherited actions
		String inheritJoined = getDefinitionElement().attributeValue("inherit");
		if (inheritJoined != null) {
			String[] inheritArray = inheritJoined.split("\\|");
			for (int i = 0; i < inheritArray.length; i++) {
				String actionName = inheritArray[i];
				ActionInterface action = (ActionInterface) getParent()
						.getToken(actionName);
				if (action != null)
					addAction(action);
				else {
					System.err.println(Configuration
							.listPathToString(getPath()));
					System.err.println("inherited action not found: "
							+ actionName);
				}
			}
		}

		// adds all sub properties/actions based on definition
		// (if definition is not a glob {*}
		Collection<DefinitionWrapper> defs = getSubDefinitions().values();
		for (Iterator<DefinitionWrapper> i = defs.iterator(); i.hasNext();) {
			DefinitionWrapper def = i.next();
			if (!def.isGlob()) {
				if (def.isProperty())
					addProperty(new Property(def.definition));
				try {
					if (def.isAction()) {
						ActionInterface a = ActionProducer
								.getAction(def.definition);
						addAction(a);
					}
				} catch (JmcException e) {
					System.err.println("Error initializing action: "
							+ def.getName());
				}
			}
			// else {
			// if (def.isProperty())
			// addPropertyGlob(def.getName());
			// }
		}

		// as actions are ready to be inherited we may load levels
		for (Iterator<DefinitionWrapper> i = defs.iterator(); i.hasNext();) {
			DefinitionWrapper def = i.next();
			if (!def.isGlob()) {
				if (def.isLevel())
					addLevel(new Level(this, def.definition));
			}
			// else {
			// if (def.isLevel())
			// addLevelGlob(def.getName());
			// }
		}

	}*/

	/**
	 * sets the parent and also adds inheritances
	 */
	public void setParent(Level parent) {
		this.parent = parent;
	}

	public Element getConfigElement() {
		return config;
	}

	public Element getDefaultConfigElement(int sequence) {
		String name;
		if (sequence >= 0) {
			name = definition.attributeValue("name");
			name = name.replace("{*}", Integer.toString(sequence));
		} else {
			name = getDefName();
		}
		Element el = DocumentFactory.getInstance().createElement(
				definition.getName());
		el.addAttribute("name", name);
		return el;
	}

	public Element getDefinitionElement() {
		return definition;
	}

	public String getDefName() {
		return definition.attributeValue("name");
	}

	public String getName() {
		return config.attributeValue("name");
	}

	public void setName(String name) {
		config.addAttribute("name", name);
	}

	public static int getSequenceFromName(String name) {
		// TODO more robust sequence selection
		String number = name.replaceAll("\\D", "");
		return Integer.parseInt(number);
	}

	public int getSequence() {
		if (getDefName().contains("{*}")) {
			return getSequenceFromName(getName());
		} else {
			return -1;
		}
	}

	// private Level getWalkingRoot() {
	// if (hasParent())
	// return getParent().getWalkingRoot();
	// else
	// return this;
	// }

	public static Level getRootInstance() throws JmcException {
		if (root == null) {
			root = new Level(null, Configuration.getInstance()
					.getRootDefinitionElement(), Configuration.getInstance()
					.getRootConfigElement());
		}
		return root;
	}

	public Level getParent() {
		return parent;
	}

	public boolean hasParent() {
		return (parent != null);
	}

	public List<String> getPath() {
		if (hasParent()) {
			List<String> path = new LinkedList<String>(parent.getPath());
			path.add(getName());
			return path;
		} else {
			List<String> path = new LinkedList<String>();
			path.add(getName());
			return path;
		}
	}

	public void execute(UserInterfaceSession interfaceSession, String[] args)
			throws JmcException {
		interfaceSession.setLevel(this);
	}

	public Property getProperty(String name) {
		return properties.get(name);
	}

	public Hashtable<String, Property> getProperties() {
		return properties;
	}

	public boolean hasProperty(String name) {
		// TODO add default properties and globs
		return (getProperty(name) != null);
	}

	/**
	 * if such property exists overwrites it
	 * 
	 * @param newProperty
	 */
	public void addProperty(Property newProperty) {
		properties.put(newProperty.getName(), newProperty);
		newProperty.setParent(this);
		if (newProperty.config.getParent() != config) {
			Element sub = getSubElementWithName(config, newProperty.getName());
			if (sub == null) {
				config.add(newProperty.config);
			} else {
				// replacing
				config.remove(sub);
				config.add(newProperty.config);
			}
		}

	}

	public boolean hasToken(String name) {
		return (getToken(name) != null);
	}

	public Token getToken(String name) {
		return tokens.get(name);
	}

	public Hashtable<String, Token> getTokens() throws JmcException {
		return tokens;
	}

	/**
	 * if such token(level/action) exists overwrites it
	 * 
	 * @param newProperty
	 */
	// public void addToken(Token newToken) {
	// System.err.println("shall never be used");
	// // tokens.put(newToken.getName(), newToken);
	// // newToken.setParent(this);
	// }
	public void addAction(ActionInterface action) {
		tokens.put(action.getName(), action);
		actions.put(action.getName(), action);
		// action.setParent(this); sensless
	}

	public static Element getSubElementWithName(Element el, String name) {
		for (Iterator i = el.elements().iterator(); i.hasNext();) {
			Element sub = (Element) i.next();
			if (sub.attributeValue("name").compareTo(name) == 0)
				return sub;
		}
		return null;
	}
	
	public static List<Element> getSubElementsMatchingGlob(Element el, String glob) {
		List<Element> out = new LinkedList<Element>();
		for (Iterator i = el.elements().iterator(); i.hasNext();) {
			Element sub = (Element) i.next();
			if (matchesGlob(glob,sub.attributeValue("name")))
				out.add(sub);
		}
		return out;
	}
	
	public static boolean matchesGlob(String glob, String name) {
		String regexp = glob.replace("{*}", "(\\d*)");
		return name.matches(regexp);
	}

	public void addLevel(Level level) {
		tokens.put(level.getName(), level);
		levels.put(level.getName(), level);
		if (level.config.getParent() == null) {
			Element sub = getSubElementWithName(config, level.getName());
			if (sub == null) {
				config.add(level.config);
			} else {
				// TODO should I merge these trees??
				// right now let's replace it
				config.remove(sub);
				config.add(level.config);
			}
		} else if (level.config.getParent() != config) {
			System.err.println("inconsistent tree");
		}
	}

	/**
	 * I'm not validating if such command have any sense
	 * 
	 * @param name
	 */
	public void removeLevel(String name) {
		tokens.remove(name);
		levels.remove(name);
		Element elRemove = getSubElementWithName(config, name);
		config.remove(elRemove);
	}

	/**
	 * I'm not validating if such command have any sense
	 * 
	 * @param name
	 */
	public void removeProperty(String name) {
		properties.remove(name);
		Element elRemove = getSubElementWithName(config, name);
		config.remove(elRemove);
	}

	public boolean hasSubDefinition(String name) {
		return (getSubDefinition(name) != null);
	}

	public DefinitionWrapper getSubDefinition(String name) {
		if (name.matches(".*\\d*.*")) {
			name = name.replaceAll("\\d+", "{*}");
			return definitions.get(name);
		} else
			return definitions.get(name);
	}

	public Hashtable<String, DefinitionWrapper> getSubDefinitions() {
		return definitions;
	}

	public DefinitionWrapper addSubDefinition(DefinitionWrapper def) {
		definitions.put(def.getName(), def);
		return def;
	}

	public DefinitionWrapper addSubDefinition(Element def) {
		DefinitionWrapper defWrap = new DefinitionWrapper(def);
		definitions.put(def.attributeValue("name"), defWrap);
		return defWrap;
	}

	// /**
	// * there is no controle if element already exists - use with caution!
	// *
	// * @param conf
	// */
	// private void addSubConfigForLevel(Element conf) {
	// config.add(conf);
	// }

	// private boolean hasSubConfigForLevel(String name) {
	// for (Iterator i = config.elementIterator("level"); i.hasNext();) {
	// Element el = (Element) i.next();
	// if (el.attributeValue("name").compareTo(name) == 0)
	// return true;
	// }
	// return false;
	// }

	// private Element getSubConfigForLevel(String name) {
	// for (Iterator i = config.elementIterator("level"); i.hasNext();) {
	// Element el = (Element) i.next();
	// if (el.attributeValue("name").compareTo(name) == 0)
	// return el;
	// }
	// return null;
	// }

	public DefinitionWrapper getDefinitionWrapper() {
		return new DefinitionWrapper(definition);
	}

	// /**
	// * for the purpose of initializing
	// *
	// */
	// public void browseLevels() {
	// for (Iterator i = levels.values().iterator(); i.hasNext();) {
	// Level subLevel = (Level)i.next();
	// subLevel.browseLevels();
	// }
	// }
	/**
	 * enter address in "/config/network/hostname" format
	 */
	public static String getPropertyValue(String address) {
		try {
			Level level = getRootInstance();
			return level.getSubPropertyValue(address);
		} catch (JmcException e) {
			System.err.println("Shall never occure");
			return null;
		}
	}

	private String getSubPropertyValue(String address) {
		String words[] = address.split("/");
		String word = words[1];
		if (words.length == 2) {
			Property p = getProperty(word);
			if (p != null)
				return p.getValue();
			else
				return null;
		} else {
			Token tl = getToken(word);
			if (!(tl instanceof Level))
				return null;
			Level sl = (Level) tl;
			if (sl != null)
				return sl.getSubPropertyValue(address.substring(address
						.indexOf("/", 1)));
			else
				return null;

		}
	}

	// private void addPropertyGlob(String name) {
	// System.out.println("adding property glob:"+name);
	// propertyGlobs.put(name, name);
	// }
	public DefinitionWrapper getPropertyGlob(String name) {
		DefinitionWrapper def = definitions.get(name);
		if (def.isProperty() && def.isGlob())
			return def;
		else
			return null;
	}

	public Hashtable<String, DefinitionWrapper> getPropertyGlobs() {
		Hashtable<String, DefinitionWrapper> globs = new Hashtable<String, DefinitionWrapper>();
		for (Enumeration en = definitions.keys(); en.hasMoreElements();) {
			String key = (String) en.nextElement();
			DefinitionWrapper def = definitions.get(key);
			if (def.isProperty() && def.isGlob())
				globs.put(key, def);
		}
		return globs;
	}

	// private void addLevelGlob(String name) {
	// System.out.println("adding level glob:"+name);
	// levelGlobs.put(name, name);
	// }
	public DefinitionWrapper getLevelGlob(String name) {
		DefinitionWrapper def = definitions.get(name);
		if (def.isLevel() && def.isGlob())
			return def;
		else
			return null;
	}

	public Hashtable<String, DefinitionWrapper> getLevelGlobs() {
		Hashtable<String, DefinitionWrapper> globs = new Hashtable<String, DefinitionWrapper>();
		for (Enumeration en = definitions.keys(); en.hasMoreElements();) {
			String key = (String) en.nextElement();
			DefinitionWrapper def = definitions.get(key);
			if (def.isLevel() && def.isGlob())
				globs.put(key, def);
		}
		return globs;
	}

	public String getHelpString() {
		return getDefinitionElement().attributeValue("help",
				"No help available for this level.");
	}
}
